
#include <system_log.h>
#include <ff_plugin.h>

extern FFSdkUnity::Plugin g_plugin;

extern "C" {

// Special C# function for getting "OnRenderEvent" function pointer. This function will be called later by Unity engine
// in the rendering thread.
// Please refer for more details: https://github.com/Unity-Technologies/NativeRenderingPlugin
//
// Look at the PluginSource/source/RenderingPlugin.cpp,
// extern "C" UnityRenderingEvent UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API GetRenderEventFunc()
//
// and UnityProject/Assets/UseRenderingPlugin.cs, GL.IssuePluginEvent(GetRenderEventFunc(), 1);

enum FFGlRenderEventId : int
{
    FRAME_FLOW_ACTIVATE_EVENT_ID = 776,
    FRAME_FLOW_PREDICT_EVENT_ID = 778,
    FRAME_FLOW_PROVIDE_DATA_EVENT_ID = 781
};

#define GL_WRAPPER g_plugin.GetWrapperGL()
#define GL_ON_RENDER_EVENT OnRenderEventGL

FFBool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_IsActive() noexcept
{
    return GL_WRAPPER.OnIsActive();
}

FFResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_Activate()
{
    return GL_WRAPPER.OnActivate();
}

FFResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_Deactivate()
{
    return GL_WRAPPER.OnDeactivate();
}

FFResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_SetPredictionAlgorithm(
    FFPredictionAlgorithm algorithm, bool extrapolation)
{
    FFLOGI("Unity_FrameFlow_SetPredictionAlgorithm start");
    return GL_WRAPPER.OnSetPredictionAlgorithm(algorithm, extrapolation);
}

FFResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_SetResolution(
        int width, int height)
{
    return GL_WRAPPER.OnSetResolution(static_cast<uint32_t>(width), static_cast<uint32_t>(height));
}

FFResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_SetResolution2(FFResolutionInfo2 const *pResolutionInfo)
{
    return GL_WRAPPER.OnSetResolution2(pResolutionInfo);
}

FFResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_SetCvvZSemantic(FFCvvZSemantic semantic)
{
    return GL_WRAPPER.OnSetCvvZSemantic(semantic);
}

FFResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_SetStencilRemapInfo(FFStencilValueSemantic const *pRemapTable)
{
    return GL_WRAPPER.OnSetStencilRemapInfo(pRemapTable);
}

FFResult UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_ProvidePerFrameData(FFPerFrameData const *pPerFrameData)
{
    return GL_WRAPPER.OnProvidePerFrameData(pPerFrameData);
}

static void UNITY_INTERFACE_API GL_ON_RENDER_EVENT(int eventID, void *data)
{
    FFResult result = FF_SUCCESS;
    switch (eventID) {
        case FFGlRenderEventId::FRAME_FLOW_ACTIVATE_EVENT_ID: {
            result = GL_WRAPPER.OnActivate();
            break;
        }
        case FFGlRenderEventId::FRAME_FLOW_PREDICT_EVENT_ID: {
            // Note it's undocumented convention. If the OpenGL handle is negative it's considered as texture object.
            // If it's not negative it's a framebuffer object.
            // In Unity integration version we uses texture objects. So to reduce misunderstanding in C# code
            // we siletly pass negative value to the GameSDK.
            auto const raw = reinterpret_cast<std::ptrdiff_t>(data);
            result = GL_WRAPPER.OnDrawFrame(static_cast<int32_t>(-raw));
            break;
        }
        case FFGlRenderEventId::FRAME_FLOW_PROVIDE_DATA_EVENT_ID: {
            result = GL_WRAPPER.OnProvidePerFrameData(static_cast<FFPerFrameData const *>(data));
            break;
        }
        default: {
            FFLOGD("OnRenderEvent - Unexpected event %d", eventID);
            break;
        }
    }

    if (result != FF_SUCCESS) {
        FFLOGW("OnRenderEvent - Event call %d returned: %d", eventID, static_cast<int>(result));
    }
}

UnityRenderingEventAndData UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API FrameFlow_GetRenderEventFunc()
{
    return &GL_ON_RENDER_EVENT;
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

} // extern "C"
